const util = require('util');
const execSync = require('child_process').execSync
const { spawn } = require('child_process');
const fs = require('fs')
const path = require('path')





//change the node pwd
process.chdir(path.dirname(process.argv[1]));

//当前目录
const CWD_PATH = process.cwd();

//日志缓存
let CACHE_LOG_DATE = ""

exports.INFO = INFO = msg => {
	if(msg.indexOf("\n") === -1)
		msg+="\n";
	CACHE_LOG_DATE+= msg
	console.log(msg)
}

exports.ERROR = ERROR =  msg => {
	CACHE_LOG_DATE+= "[ERROR:]"+msg+"\n"
	console.log('\x1B[31m%s\x1B[0m', msg)
	throw msg;
}

/**
@desc 将所有info缓存的数据，写入到*.log中 clsp中请调用LOG
@param file:String  
*/
exports.OUT_LOG = OUT_LOG = file => fs.writeFileSync(file,CACHE_LOG_DATE,{encoding:"utf-8"})


/**
	@Desc 同步运行系统命令 直到命令完全结束才返回数据
	@Param cmd:String 命令字符串 
	@Param printFlag:String 设置自动打印输出 默认：打印
*/
exports.SHELL = SHELL = (cmd,isPrint=true)=>{

	//因为exec命令 会对 ' 符号进行一次转换
	if(cmd.trim().indexOf("opc") == 0 && cmd.indexOf(`'`) > -1)
		throw `please use " instead of ',example opc '(1 2)' -> opc "(1 2)"`

	isPrint &&	INFO(`> ${cmd}`)

	try{

		const result = execSync(cmd).toString();

		isPrint && INFO(`~ ${result}`)

		return result.replace(/[\r\n]/g,"");

	}catch(e){
		throw `调用失败:${e.stdout.toString()}`
	}


}


/**
	@Desc 运行系统命令 异步返回信息
	@Param cmd:String 命令字符串 
	@Param args:[String] 参数数组
	@Param printFlag:String 设置自动打印输出 默认：打印
*/
exports.SHELL_ASYNC = SHELL_ASYNC = async (cmd,args,isPrint=true) => {

	const print = msg => isPrint &&	INFO(msg.toString())

	print(`> ${cmd} ${args}`)

	const ls = spawn(cmd,args);

	return new Promise((resolve,reject)=>{

		let dataSum = "";

		ls.stdout.on('data', d => {
			print(d)
			dataSum+=d;
		});

		ls.stderr.on('data', print);

		ls.on('close', (code) => {
		  resolve(ls)
		});

	})

}

exports.XCH_RATE = XCH_RATE = 1000000000000;

//chia 1xch等于XCH_RATE mojo  实际都以mojo运行
exports.XCH_TO_MOJO = XCH_TO_MOJO = num => num * XCH_RATE;

//将mojo兑换成xch
exports.MOJO_TO_XCH = MOJO_TO_XCH = (numStr) => {
	const num = numStr/XCH_RATE;
	const m = num.toExponential().match(/\d(?:\.(\d*))?e([+-]\d+)/);
	return num.toFixed(Math.max(0, (m[1] || '').length - m[2]));
}


exports.RANDOM_STRING = RANDOM_STRING = e => {    
    e = e || 32;
    var t = "ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678",
    a = t.length,
    n = "";
    for (i = 0; i < e; i++) n += t.charAt(Math.floor(Math.random() * a));
    return n
}

//创建临时文件
exports.CREATE_TEMP_FILE = CREATE_TEMP_FILE = (fileName,content) => {
	const t = fileName.split(".")
	const realName = `${RANDOM_STRING(4)}_${t[0]}_runtime.${t[1]}`
	const tempFile = path.join(__DIRNAME,realName)
	TEMP_FILES.push(tempFile)
	fs.writeFileSync(tempFile,content,{encoding:"utf-8"})
	return {realName,tempFile};
}

exports.isInteger = isInteger = obj => typeof obj === 'number' && obj%1 === 0
	
exports.isString = isString = obj => typeof obj === 'string'

exports.convertToStdHex = convertToStdHex = hexStr => hexStr.indexOf("0x") === 0 ? hexStr.substring(2,hexStr.length) : hexStr;





;const LOG_FILE = "/home/yeqin/gitRepository/chialisp-train1/recourse/map3_runtime.clspLog"; 
let PREFIX = 'txch'; 
const JS_FILE = "/home/yeqin/gitRepository/chialisp-train1/recourse/map3_runtime.js"; 
const FileName = "map3.clsp"; 
const WORK_DIR = "/home/yeqin/gitRepository/chialisp-train1/recourse"; 
let DEBUG_FLAG = false; 
const TEMP_FILES = ["/home/yeqin/gitRepository/chialisp-train1/recourse/map3_runtime.clspLog","/home/yeqin/gitRepository/chialisp-train1/recourse/map3_runtime.js"]; 
const __DIRNAME = __dirname; 
let __isDeploy__ = 0;const PYTHON_LIB = "/home/yeqin/gitRepository/chialisp-preclsp/pythonLib"; 
;

/** 内置API */

/**
@desc 运行哈希谜题
@param puzzle:String 谜题
@param solution:String 答案
*/
const BRUN = (puzzle,solution) => SHELL(`brun "${puzzle}" "${solution}"`)


/**
 开启debug模式
*/
const DEBUG = () => DEBUG_FLAG = true

/**
  删除js
*/
const OVER_EVENT = (e)=> {

	if(__isDeploy__ >=1 ){
		console.log(`deploy time:${__isDeploy__}`)
	}

	if(e)
			throw e

	if(__isDeploy__ == 1 || __isDeploy__ == 0){

		if(DEBUG_FLAG){
			OUT_LOG(LOG_FILE)
		}else{
			TEMP_FILES.forEach(f => {
				fs.existsSync(f) && fs.rmSync(f);
			})
		}
	}else{
		__isDeploy__--;
		__runWrapFunction_()
	}

}	

//定义全局变量，用于#！clsp文件异常退出使用
process.on('uncaughtException', function(err) {
	ERROR(`异常退出:${err}`)
	OVER_EVENT()
});


/** clsp命令 */

/**
@desc 断言方法
*/
const ASSERT = (title,condition) => {
	if(!condition)
		ERROR(title)
}

const ASSERT_HAS = (result,hasCotent) => {
	if(result.indexOf(hasCotent) === -1){
		INFO("断言错误退出")
		ERROR(result)
	}
}


/**
@desc将拼图哈希转成地址
@param puzzlehash:String 拼图的哈希值
@param prefix:String 地址前缀 默认txch
*/
const ADDRESS = (puzzleHash,prefix='txch') => SHELL(`cdv encode ${puzzleHash} --prefix ${prefix}`)



const CURRY = (puzzleFile,params,extInstruct="")  => {
	const paramStr = params.reduce((s,v)=> s+=` -a ${v}`,"")
	return SHELL(`cdv clsp curry ${puzzleFile} ${paramStr} ${extInstruct}`)
}


/**
@desc 生成拼图hash
@param params:[Object] curry参数
*/
const TREE_HASH = (puzzleFile,params) => CURRY(puzzleFile,params,"--treehash")


/**
@desc 生成拼图hash
@param params:[Object] curry参数
*/
const PUZZLE_REVEAL = (puzzleFile,params) => CURRY(puzzleFile,params,"-x")


/**
@desc 生成一个拼图的所有信息
@param puzzleFile:String 拼图文件路劲
@param params:[Object] curry参数
@param prefix:String 地址前缀 默认txch
*/
const PUZZLE_RESOLVE = (puzzleFile,params) => {
	const curry = CURRY(puzzleFile,params)
	const treehash = TREE_HASH(puzzleFile,params)
	const address = ADDRESS(treehash,PREFIX)
	const reveal = PUZZLE_REVEAL(puzzleFile,params)
	return {treehash,address,reveal,curry}
}


const DEPLOY_PUZZLE = (fingerprint,amount,address)=> {
	return SHELL(`chia wallet send -a ${MOJO_TO_XCH(amount)} -t ${address} -f ${fingerprint}`)
}

const TRANSACTION_STATE = (fingerprint,transactionId,saveLog=true) => SHELL(`chia wallet get_transaction -f ${fingerprint} -tx ${transactionId} `,saveLog)

const COIN_RECORDS_HASH = hash => JSON.parse(SHELL(`cdv rpc coinrecords  --by puzzlehash ${hash}`))

const WAIT = s => new Promise((r) => setTimeout(r,s*1000))

const OPC = solution => SHELL(`opc ${JSON.stringify(solution)}`)

const PUSH_TX = fileName => SHELL(`cdv rpc pushtx ${fileName}`)

const DELETE_UNCONFIRMED_TRANSACTION = fg => SHELL(`chia wallet delete_unconfirmed_transactions -f ${fg}`)

const DEPLOY = (()=>{

	let addTime=0;

	return (count)=>{

		if(addTime++ == 0)
			__isDeploy__=count;

	}

})();



;



//拼图操作
class Puzzle {

	constructor(fsPath){
		this.fsPath = fsPath;
	}

	//柯里化
	curry(...params){
		return this.curryList(params)
	}
	curryList(params){
		return new PuzzleCurryOperate({puzzle:this,curryParam:params}).initData()
	}

	static of(puzzleFile){
		return new Puzzle(puzzleFile)
	}
}

//拼图柯里化后操作
class PuzzleCurryOperate {

	constructor(ext){
		this.ext = ext;
	}

	getPuzzleHash(){
		return this.ext.treehash;
	}

	initData(){
		const {puzzle,curryParam} = this.ext;
		const data = PUZZLE_RESOLVE(puzzle.fsPath,curryParam)
		this.ext = {...this.ext,...data}
		return this;
	}

	curry(...curryParam){
		const {curry} = this.ext;
		const data = PUZZLE_RESOLVE(`"${curry}"`,curryParam)
		this.ext = {...this.ext,...data}
		return this;
	}

	// run(solution){
	// 	const {curry} = this.ext;
	// 	BRUN(curry,`(${solution.join(" ")})`)
	// }

	run(solution){
		const {curry} = this.ext;
		BRUN(curry,solution)
	}

	getCoins(){
		return COIN_RECORDS_HASH(this.ext.treehash).map(baseCoin => new DeployCoin(this.ext,baseCoin))
	}

	getUnSpentCoins(amount){
		const coinList = this.getCoins().filter(dc => !dc.isSpent())
		return amount != undefined ? coinList.filter(s => s.getAmount() == amount) : coinList;
	}

	getSpentCoins(amount){
		const coinList = this.getCoins().filter(dc => dc.isSpent())
		return amount != undefined ? coinList.filter(s => s.getAmount() == amount) : coinList;
	}


	deploy(fingerprint,amount){

		const {address} = this.ext;

		const depolyResult = DEPLOY_PUZZLE(fingerprint,amount,address)

		ASSERT_HAS(depolyResult,"Do chia wallet get_transaction")

		const transactionId = depolyResult.match(/\s0x\w+\s/g)[0]

		return new TransactionOperate({
			...this.ext,
			puzzleCurryOperate:this,
			fingerprint,
			transactionId,
			amount
		})

	}

}

//部署成功操作
class TransactionOperate {

	constructor(ext){
		this.ext = ext
	}

	waitConfirmCall(delay=10,cb){
		const {fingerprint,transactionId,amount} = this.ext;
		const flag = setInterval(()=>{

			const result = TRANSACTION_STATE(fingerprint,transactionId,false)

			console.log(result)

			if(result.indexOf("Status: Confirmed") > -1){
				clearInterval(flag)
				cb(this)
			}

		},delay*1000)
	}

	waitConfirm(delay){
		return new Promise((resolve)=> this.waitConfirmCall(delay,resolve))
	}

	//当交易成功后，获得一个未花费且数量相等的货币
	async getCoin(){
		const {puzzleCurryOperate,amount} = this.ext
		await this.waitConfirm()
		const coins = puzzleCurryOperate.getUnSpentCoins(amount)
		coins.length == 0 && ERROR("no coin found!")
		return coins[0]
	}

}

//基础货币
class Coin {
	constructor(baseCoin){
		this.ext = {baseCoin}
	}

	isSpent(){
		return this.ext.baseCoin.spent;
	}

	getId(){
		return SHELL(`run "(sha256 ${this.getParentCoin()} ${this.getPuzzleHash()} ${this.getAmount()} )"`)
	}

	getAmount(){
		return this.ext.baseCoin.coin.amount;
	}

	getParentCoin(){
		return this.ext.baseCoin.coin.parent_coin_info;
	}

	getPuzzleHash(){
		return this.ext.baseCoin.coin.puzzle_hash;
	}

	getBaseSpentCoinInfo(){
		const removeHex = x => x.substring(2,x.length)
		return {
			amount:this.getAmount(),
			parent_coin_info:removeHex(this.getParentCoin()),
			puzzle_hash:removeHex(this.getPuzzleHash())
		}
	}

	attr(a){
		switch(a){
			case "id":return this.getId();
			case "puzzle_hash":return this.getPuzzleHash();
			case "amount":return this.getAmount();
			default:
				throw `not set the attribute:${a}`
		}
	}

		//获取属性
	attrs(attrList){
		return attrList.map(this.attr.bind(this))
	}

	equal(coin){
		return this.getAmount() == coin.getAmount() 
		&& this.getParentCoin() == coin.getParentCoin()
		&& this.getPuzzleHash() == coin.getPuzzleHash()
	}

	print(){
		console.log(JSON.stringify(this,null,2))
	}

}

//部署的货币
class DeployCoin extends Coin {

	constructor(ext,baseCoin){
		super(baseCoin)
		//this.ext 必须在后面，这样baseCoin的属性可以覆盖原先的
		this.ext = {...ext,...this.ext}
	}

	setSolution(solution){
		this.ext.solutionParam = solution;
		this.ext.solution = OPC(`(${solution.join(" ")})`);
		return this;
	}

	setSignature(params,privateKey){
		this.ext.signature = {
			privateKey,
			params
		}
	}

	setSolutionWithSignature(solution,privateKey){
		this.setSolution(solution)
		this.setSignature(solution,privateKey)
	}

	run(solution){
		const {curry} = this.ext;
		BRUN(curry,`(${solution.join(" ")})`)
	}

	getSpentInfo(){
		if(!this.ext.solutionParam)
			ERROR(`coin not set solution ${this.ext}`)

		const {reveal:puzzle_reveal,solution} = this.ext;

		return {
			coin:this.getBaseSpentCoinInfo(),
			puzzle_reveal,
			solution
		}
	}


	doSpent(){
		return Rpc.of([this]).spent()
	}

	//获取已经花费的货币
	getSelfSpentInfo(){
		return new PuzzleCurryOperate(this.ext).getSpentCoins(this.getAmount()).find(c => c.equal(this))
	}




}

class Rpc {


	no_aggregated_signature="c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000";

	constructor(deployCoins){
		this.deployCoins = deployCoins;
	}

	getSignature(){
		const signatureList = this.deployCoins.map(c => c.ext.signature).filter(v => v)
		
		switch(signatureList.length){
			case 0:return this.no_aggregated_signature;
			case 1: return PRIVATE_SIGNATURE(signatureList[0])
			default:
				return AGGREGATE_SIGNATURE(signatureList)
		}

	}

	spent(){

		const {deployCoins,no_aggregated_signature} = this;

		const spentJson = {
			aggregated_signature: this.getSignature(),
			coin_spends:deployCoins.map(c => c.getSpentInfo())
		}

		console.log(JSON.stringify(spentJson,null,2))

		const {realName} = CREATE_TEMP_FILE("spent.json",JSON.stringify(spentJson,null,2))

		const spentMsg = JSON.parse(PUSH_TX(realName))

		if(spentMsg.success){
			spentMsg.getSpendCoins = ()=> this.getAlreadySpentCoinList()
		}

		return spentMsg;
	}

	//获取已经消费的货币
	getAlreadySpentCoinList(){
		const {deployCoins} = this;
		return new Promise( (resolve,reject) => {

			const flag = setInterval(()=>{

				const spentInfo = deployCoins[0].getSelfSpentInfo()

				if(spentInfo){

					const lastSpent = deployCoins.slice(1,deployCoins.length).map(c => c.getSelfSpentInfo())

					clearInterval(flag)

					resolve([spentInfo,...lastSpent])

				}

			},30*1000)

		})
	}

	// setSignature(privateKey,solutionParam){
	// 	this.no_aggregated_signature = PRIVATE_SIGNATURE(privateKey,solutionParam)
	// 	return this;
	// }


	static of(coins){
		return new Rpc(coins)
	}

}







;


const __runPythonLibFile = (pythonFile,extParamStr) => SHELL(`python3 ${ path.join(PYTHON_LIB,pythonFile) } ${extParamStr.join(" ")}`)

const __convertParam = params => params.map(p => {

	if(isInteger(p)){
		return "int="+p;
	}

	if(isString(p)){
		if(p.indexOf("0x") === 0){
			return "hex="+convertToStdHex(p)
		}
		return "str="+p;
	}

	throw "param sign error"

})

const PRIVATE_SIGNATURE = ({privateKey,params}) => {
	return __runPythonLibFile("sign.py",__convertParam([privateKey,...params]))
}

const AGGREGATE_SIGNATURE = signatureList => {
	const extAgs = signatureList.map(s => {
		const args = __convertParam([s.privateKey,...s.params])
		return `"${args.join(" ")}"`
	})
	return __runPythonLibFile("aggregateSign.py",extAgs)
};

const OPERATE_DEPLOY = async args => {

	const {curry,fingerPrint,amount,file,size=1} = args;

	const puzzle = Puzzle.of(file).curryList(curry);

	const coins = puzzle.getUnSpentCoins(amount);

	const deployCoins = async ()=>{
		const transaction = puzzle.deploy(fingerPrint,amount)
		const coin = await transaction.getCoin()
		return [puzzle,coin]
	}

	//deploy pattern
	if(__isDeploy__ > 0){

		const _deployCoins =[]
		for(var i=0;i<size;i++){
			const [p,c] = await deployCoins()
			_deployCoins.push(c)
		}
		return [puzzle,_deployCoins[0],_deployCoins]
	}else{

		if(coins.length >= size){
			return [puzzle,coins[0],coins]
		}
		await deployCoins()
		return await OPERATE_DEPLOY(args)
		
	}


};
(global.__runWrapFunction_ = async ()=>{  ;
;Puzzle.of("\"map3.clsp\"").curry().run('( (101 102 103 104) )')
;
OVER_EVENT();
})().catch(OVER_EVENT);
